﻿var $System = { };

$System.Console = { };

$System.Console.execCmdLine = function()
{
    var dom = document.getElementById("input");
    var str = dom.value;
    dom.value = "";
    
    if ( str == "" )
        str = null;
    $System.Console.WriteLine(str);
    
    dom.focus();
    
    if ( $System.Console.OnInput != null )
        $System.Console.OnInput(str);
};        

$System.Console.dom = null;

$System.Console.OnInput = null;

$System.Console.WriteLine = function(str)
{
    if ( $System.Console.dom == null )
    {
        var d = document.createElement("div");
        var text = document.createTextNode(!str ? String.fromCharCode(160) : str)
        d.appendChild( text );
        document.getElementById("output").appendChild(d);
    }
    else
    {
        $System.Console.dom.appendData(!str ? String.fromCharCode(160) : str);
        $System.Console.dom = null;
    }
};

$System.Console.Write = function(str)
{
    if ( $System.Console.dom == null )
    {
        var d = document.createElement("div");        
        $System.Console.dom = document.createTextNode( str == null ? "null" : str.toString())
        d.appendChild( $System.Console.dom );
        document.getElementById("output").appendChild(d);
    }
    else
    {
        $System.Console.dom.appendData(str);
    }
};

$System.Console.WriteXML = function(node)
{
    $System.Console.dom = null;
    document.getElementById("output").appendChild(node);
};

$System.onloadHandlers = [ ];

$System.onload = function()
{
    for( var i = 0; i < $System.onloadHandlers.length; ++i )
    {
        $System.onloadHandlers[i]();
    }
};

$System.extend = function(type, baseType, interfaces, members)
{
    type.$baseType = baseType;
    if ( baseType )
        type.$inherits = [ baseType ].concat( interfaces );
    else
        type.$inherits = interfaces;
    
    // Class inheritance
    if ( baseType )
    {
        for( var key in baseType.prototype )
            type.prototype[key] = baseType.prototype[key];
    }
    // Class members
    if ( members )
    {
        for( var key in members )
        {
            var val = members[key];
            type.prototype[key] = val;
        }
    }
};

$System.as = function(that, type)
{
    return $System.isA(that, type) ? that : null;
};

$System.isA = function(that, type)
{
    // Check for null
    if ( !that || !type )
        return false;
    if ( that.$type == type )
        return true;
    if ( !that.$type || !that.$type.$inherits )
        return false;
    return $System.internalIsA( that.$type, type );
};

$System.internalIsA = function( test, type )
{
    if ( !test.$inherits )
        return false;
    for( var i = 0; i < test.$inherits.length; ++i )
    {
        if ( test.$inherits[i] == type )
            return true;
        if ( $System.internalIsA( test.$inherits[i], type ) )
            return true;
    }
    return false;
};

/// <public />
/// Constructs a GUID out of random numbers.
/// However, a better GUID would involve IP addresses etc.
/// but we do not have it.
$System.CreateGUID = function()
{    
    var sep = "-";
    var str = "";    
    for( var i = 0; i < 8; ++i )
    {
        var v = Math.floor( Math.random() * 0xFFFF );
		var vstr = v.toString(16);
		while( vstr.length < 4 )
			vstr = "0" + vstr;
        if ( i <= 1 )
            str += vstr;
        else if ( i <= 5 )
            str += sep + vstr;
        else
            str += vstr;
    }
    return str;
};

$System.op_assignadd = function(obj, getter, setter, value)
{
    var result = obj[getter].call(obj) + value ;
    obj[setter].call( obj, result );
    return result;
};

$System.op_preinc = function(obj, getter, setter)
{
    var result = obj[getter].call(obj) + 1 ;
    obj[setter].call( obj, result );
    return result;
};

$System.op_postinc = function(obj, getter, setter)
{
    var result = obj[getter].call(obj) ;
    obj[setter].call( obj, result + 1 );
    return result;
};

$System.op_predec = function(obj, getter, setter)
{
    var result = obj[getter].call(obj) - 1 ;
    obj[setter].call( obj, result );
    return result;
};

$System.op_postdec = function(obj, getter, setter)
{
    var result = obj[getter].call(obj) ;
    obj[setter].call( obj, result - 1 );
    return result;
};

$System.funcObj = function(funcName, obj)
{
    var func = obj[funcName];
    return function() { return func.apply( obj, arguments ) };
};


$System.inlineFunc = function(func, obj)
{
    return function() { return func.apply( obj, arguments ) };
};

$System.emitEvent = function(event, value)
{
    if ( event )
        event.invoke(value);
};

$System.publishProperty = function(value)
{
    // The sink list must be copied, because it can change while we iterate over it
    var arr = this.sinks.slice(0, this.sinks.length);
    for( var i = 0; i < arr.length; ++i )
    {
        if ( arr[i].activeCount > 0 )
            arr[i].invoke(value);
    }
};

$System.invokePropertyBinding = function()
{
    var value = this.func.apply( this.obj, arguments );
};

$System.invokeEventBinding = function(value)
{
    var arr = this.sinks.slice(0, this.sinks.length);
    for( var i = 0; i < arr.length; ++i )
    {
        var sink = arr[i];
        if ( sink.activeCount > 0 )
            sink.invoke( value );
    }    
};

$System.invokeBinding = function()
{
    var value = this.func.apply( this.obj, arguments );
    // if ( this.getter && this.sinks.length > 0 )
        // value = this.getter.call( this.obj, null );
        
    var arr = this.sinks.slice(0, this.sinks.length);
    for( var i = 0; i < arr.length; ++i )
    {
        var sink = arr[i];
        if ( sink.activeCount > 0 )
            sink.invoke( value );
    }    
};

$System.subscribeBinding = function(sourceObj, propName, getter, setter, binding )
{
    var bind = $System.wrapPropertyAsBinding(sourceObj, propName, getter, setter);
    var result = $System.connectBindings(true, bind, binding);    
    return result;
};

$System.subscribeBindingTerm = function(sourceObj, propName, getter, setter, binding )
{
    var bind = $System.wrapPropertyAsBinding(sourceObj, propName, getter, setter);
    var result = $System.connectBindings(false, bind, binding);
    return sourceObj[getter]();
};

$System.unsubscribeBindingTerm = function(binding)
{
    for( var i = 0; i < binding.sources.length; ++i )
    {
        var s = binding.sources[i];
        var k = 0;
        while( k < s.sinks.length )
        {
            if ( s.sinks[k] == binding )
            {
                s.sinks.splice(k,1);
            }
            else
                k++;
        }
    }
    binding.sources = [];
};

$System.wrapPropertyAsBinding = function( obj, propName, getter, setter )
{
    var binding = obj[propName];
    if ( binding == null )
    {
        binding = { };
        binding.obj = obj;
        binding.func = obj[setter];
        binding.getter = obj[getter];
        binding.invoke = $System.invokePropertyBinding;
        binding.sources = [ ];
        binding.sinks = [ ];
        binding.activeCount = 1;
        binding.type = "prop";
        obj[propName] = binding;
    }
    return binding;
};

$System.wrapHTMLPropertyAsBinding = function( obj, propName, getter, setter )
{
    var binding = obj[propName];
    if ( binding == null )
    {
        binding = { };
        binding.obj = obj;
        binding.func = function(value) { obj[setter] = value };
        binding.getter = function() { return obj[getter] };
        binding.invoke = $System.invokePropertyBinding;
        binding.sources = [ ];
        binding.sinks = [ ];
        binding.activeCount = 1;
        binding.type = "prop";
        obj[propName] = binding;
    }
    return binding;
};

$System.wrapFuncAsBinding = function( obj, func )
{
    var binding = { };
    binding.obj = obj;
    binding.func = func;
    binding.invoke = $System.invokeBinding;
    binding.sources = [ ];
    binding.sinks = [ ];
    binding.activeCount = 1;
    return binding;
};

$System.wrapExprAsBinding = function( obj, func )
{
    var binding = { };
    func.binding  = binding;
    binding.obj = obj;
    binding.func = func;
    binding.invoke = $System.invokeBinding;
    binding.sources = [ ];
    binding.sinks = [ ];
    binding.activeCount = 0;
    binding.type = "expr";
    return binding;
};

$System.wrapEventAsBinding = function( obj, eventName )
{
    var binding = obj[eventName];
    if ( binding == null )
    {
        binding = { };
        binding.obj = obj;
        binding.invoke = $System.invokeEventBinding;
        binding.sources = [ ];
        binding.sinks = [ ];
        binding.activeCount = 1;
        binding.type = "event";
        obj[eventName] = binding;
    }
    return binding;
};

$System.connectBindings = function(fireIfPossible, bind1, bind2)
{
    // Get the binding object which are not just connections between yet other bindings, i.e.
    // get to those bindings which really generate/process/consume data.
    var b1 = bind1;
    var b2 = bind2;
    while( b1.right )
        b1 = b1.right;
    while( b2.left )
        b2 = b2.left;

    // Connect the two bindings    
    b1.sinks.push(b2);
    b2.sources.push(b1);
    // Propagate that there are additional listeners
    $System.changeActiveCount(b1, b2, b2.activeCount, fireIfPossible);

    // Get the source and sink end of the new binding.
    while( bind1.right )
        bind1 = bind1.left;
    while( bind2.left )
        bind2 = bind2.right;
    
    // Create a new binding
    var binding = { };
    binding.obj = bind1.obj;
    binding.func = bind1.func;
    // Redirect the invoke to bind1
    binding.invoke = function() { bind1.invoke.apply( bind1, arguments); };
    binding.sources = bind1.sources;
    binding.sinks = bind2.sinks;
    binding.left = bind1;
    binding.right = bind2;
    return binding;    
};

$System.disposeBinding = function()
{
    if ( this.left && this.right )
    {        
        for( var i = 0; i < this.left.sinks.length; ++i )
        {
            if ( this.left.sinks[i] == this.right )
            {
                this.left.sinks.splice(i,1);
                break;
            }
        }
        for( var i = 0; i < this.right.sources.length; ++i )
        {
            if ( this.right.sources[i] == this.left )
            {
                this.right.sources.splice(i,1);
                break;
            }
        }
        
        $System.changeActiveCount(this.left, this.right, -this.right.activeCount);
        
        delete this.left;
        delete this.right;
    }
};

$System.changeActiveCount = function(bind1, bind2, change, fireIfPossible)
{
    if ( change == 0 )
        return;
                
    bind1.activeCount += change;
    
    // Bind2 just became active and this is the binding of a property? -> fire
    if ( bind2.activeCount == change && bind1.type == "prop" && fireIfPossible)
    {
        bind2.invoke(bind1.getter.call(bind1.obj, null));
    }
    // Bind2 just became active and this is the binding of a property? -> fire
    else if ( bind2.activeCount == change && bind1.type == "expr" && fireIfPossible)
    {
        bind2.invoke(bind1.invoke());
    }
    
    for( var i = 0; i < bind1.sources.length; ++i )
    {
        $System.changeActiveCount(bind1.sources[i], bind1, change, fireIfPossible);
    }
};

$System.StartTimer = function( func, delay)
{
    return window.setTimeout( func, delay );
};

$System.StopTimer = function( timerID )
{
    window.clearTimeout(timerID);
};

$System.StartIntervalTimer = function( func, delay)
{
    return window.setInterval(func, delay);
};

$System.StopIntervalTimer = function( timerID )
{
    window.clearInterval(timerID);
};

$System.Debug = { };

$System.Debug.printException = function(e)
{
    if ( e.$exception )
        $System.Debug.printException(e.$exception);
    if ( e.$line )
    {
        $System.Console.WriteLine(e.$func + ":" + e.$line);
    }
    else
    {
        $System.Console.WriteLine(e.name + " in:");
    }       
}